/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "core_main.h"


#define CVARS_HASH_SIZE			512

#define MAX_LIST_CVARS			2048

static cvar_t *		cvar_variablesHashTable[CVARS_HASH_SIZE];
static cvar_t *		cvar_variables;

static bool			cvar_allowCheats = true;
static int			cvar_modifiedFlags;


/*
 ==================
 CVar_Get
 ==================
*/
cvar_t *CVar_Get (const char *name, const char *value, int flags, const char *description){

	cvar_t	*cvar;
	uint	hashKey;

	// Check if it already exists
	cvar = CVar_FindVariable(name);
	if (cvar){
		// OR in the specified flags
		cvar->flags |= flags;

		// Set the modified flags
		cvar_modifiedFlags |= cvar->flags;

		// Update a latched variable
		if (cvar->latchedValue){
			Mem_Free(cvar->value);

			cvar->value = cvar->latchedValue;
			cvar->floatValue = Str_ToFloat(cvar->latchedValue);
			cvar->integerValue = Str_ToInteger(cvar->latchedValue);
			cvar->latchedValue = NULL;
		}

		// Reset value is always set internally
		Mem_Free(cvar->resetValue);
		cvar->resetValue = Mem_CopyString(value, TAG_COMMON);

		// A read only variable is always set internally
		if (cvar->flags & CVAR_ROM){
			Mem_Free(cvar->value);

			cvar->value = Mem_CopyString(value, TAG_COMMON);
			cvar->floatValue = Str_ToFloat(value);
			cvar->integerValue = Str_ToInteger(value);
		}

		// Description is always set internally, but can be NULL
		if (description){
			if (cvar->description)
				Mem_Free(cvar->description);

			cvar->description = Mem_CopyString(description, TAG_COMMON);
		}

		cvar->modified = true;

		return cvar;
	}

	// Set the modified flags
	cvar_modifiedFlags |= flags;

	// Allocate a new cvar
	cvar = (cvar_t *)Mem_Alloc(sizeof(cvar_t), TAG_COMMON);

	cvar->name = Mem_CopyString(name, TAG_COMMON);
	cvar->value = Mem_CopyString(value, TAG_COMMON);
	cvar->resetValue = Mem_CopyString(value, TAG_COMMON);
	cvar->latchedValue = NULL;
	cvar->floatValue = Str_ToFloat(value);
	cvar->integerValue = Str_ToInteger(value);
	cvar->flags = flags;
	cvar->description = (description) ? Mem_CopyString(description, TAG_COMMON) : NULL;
	cvar->modified = true;

	// Link the cvar in
	cvar->next = cvar_variables;
	cvar_variables = cvar;

	// Add to hash table
	hashKey = Str_HashKey(name, CVARS_HASH_SIZE, false);

	cvar->nextHash = cvar_variablesHashTable[hashKey];
	cvar_variablesHashTable[hashKey] = cvar;

	return cvar;
}

/*
 ==================
 CVar_Set
 ==================
*/
static void CVar_Set (cvar_t *cvar, const char *value, bool force){

	Com_DPrintf("CVar_Set( %s, \"%s" S_COLOR_DEFAULT "\", %i )\n", cvar->name, value, force);

	// Set the modified flags
	cvar_modifiedFlags |= cvar->flags;

	// Set the new value
	if (!force){
		if (cvar->flags & CVAR_INIT){
			Com_Printf("'%s' is write protected\n", cvar->name);
			return;
		}

		if (cvar->flags & CVAR_ROM){
			Com_Printf("'%s' is read only\n", cvar->name);
			return;
		}

		if (cvar->flags & CVAR_CHEAT && !cvar_allowCheats){
			Com_Printf("'%s' is cheat protected\n", cvar->name);
			return;
		}

		if (cvar->flags & CVAR_LATCH){
			if (cvar->latchedValue){
				if (!Str_ICompare(cvar->latchedValue, value))
					return;

				Mem_Free(cvar->latchedValue);
				cvar->latchedValue = NULL;
			}

			if (!Str_ICompare(cvar->value, value))
				return;

			cvar->latchedValue = Mem_CopyString(value, TAG_COMMON);

			Com_Printf("'%s' will be changed upon restarting\n", cvar->name);
			return;
		}
	}
	else {
		if (cvar->latchedValue){
			Mem_Free(cvar->latchedValue);
			cvar->latchedValue = NULL;
		}
	}

	if (!Str_ICompare(cvar->value, value))
		return;			// Not changed

	Mem_Free(cvar->value);

	cvar->value = Mem_CopyString(value, TAG_COMMON);
	cvar->floatValue = Str_ToFloat(value);
	cvar->integerValue = Str_ToInteger(value);
	cvar->modified = true;
}

/*
 ==================
 CVar_SetString
 ==================
*/
void CVar_SetString (cvar_t *cvar, const char *value){

	CVar_Set(cvar, value, true);
}

/*
 ==================
 CVar_SetFloat
 ==================
*/
void CVar_SetFloat (cvar_t *cvar, float value){

	CVar_Set(cvar, Str_FromFloat(value), true);
}

/*
 ==================
 CVar_SetInteger
 ==================
*/
void CVar_SetInteger (cvar_t *cvar, int value){

	CVar_Set(cvar, Str_FromInteger(value), true);
}

/*
 ==================
 CVar_FindVariable
 ==================
*/
cvar_t *CVar_FindVariable (const char *name){

	cvar_t	*cvar;
	uint	hashKey;

	hashKey = Str_HashKey(name, CVARS_HASH_SIZE, false);

	for (cvar = cvar_variablesHashTable[hashKey]; cvar; cvar = cvar->nextHash){
		if (!Str_ICompare(cvar->name, name))
			return cvar;
	}

	return NULL;
}

/*
 ==================
 CVar_CompleteVariable
 ==================
*/
void CVar_CompleteVariable (const char *partial, void (*callback)(const char *)){

	cvar_t	*cvar;
	int		len;

	len = Str_Length(partial);

	// Find matching variables
	for (cvar = cvar_variables; cvar; cvar = cvar->next){
		if (Str_ICompareChars(cvar->name, partial, len))
			continue;

		callback(cvar->name);
	}
}

/*
 ==================
 CVar_GetVariableString
 ==================
*/
const char *CVar_GetVariableString (const char *name){

	cvar_t	*cvar;
	
	cvar = CVar_FindVariable(name);
	if (!cvar)
		return "";

	return cvar->value;
}

/*
 ==================
 CVar_GetVariableFloat
 ==================
*/
float CVar_GetVariableFloat (const char *name){

	cvar_t	*cvar;
	
	cvar = CVar_FindVariable(name);
	if (!cvar)
		return 0.0f;

	return cvar->floatValue;
}

/*
 ==================
 CVar_GetVariableInteger
 ==================
*/
int CVar_GetVariableInteger (const char *name){

	cvar_t	*cvar;

	cvar = CVar_FindVariable(name);
	if (!cvar)
		return 0;

	return cvar->integerValue;
}

/*
 ==================
 CVar_SetVariableString
 ==================
*/
void CVar_SetVariableString (const char *name, const char *value){

	cvar_t	*cvar;

	cvar = CVar_FindVariable(name);
	if (!cvar){
		CVar_Get(name, value, 0, NULL);
		return;
	}

	CVar_Set(cvar, value, true);
}

/*
 ==================
 CVar_SetVariableFloat
 ==================
*/
void CVar_SetVariableFloat (const char *name, float value){

	cvar_t	*cvar;

	cvar = CVar_FindVariable(name);
	if (!cvar){
		CVar_Get(name, Str_FromFloat(value), 0, NULL);
		return;
	}

	CVar_Set(cvar, Str_FromFloat(value), true);
}

/*
 ==================
 CVar_SetVariableInteger
 ==================
*/
void CVar_SetVariableInteger (const char *name, int value){

	cvar_t	*cvar;

	cvar = CVar_FindVariable(name);
	if (!cvar){
		CVar_Get(name, Str_FromInteger(value), 0, NULL);
		return;
	}

	CVar_Set(cvar, Str_FromInteger(value), true);
}

/*
 ==================
 CVar_WriteArchiveVariables
 ==================
*/
void CVar_WriteArchiveVariables (fileHandle_t f){

	cvar_t	*cvar;

	for (cvar = cvar_variables; cvar; cvar = cvar->next){
		if (!(cvar->flags & CVAR_ARCHIVE))
			continue;

		if (!cvar->latchedValue)
			FS_Printf(f, "seta %s \"%s\"" LINE_SEPARATOR, cvar->name, cvar->value);
		else
			FS_Printf(f, "seta %s \"%s\"" LINE_SEPARATOR, cvar->name, cvar->latchedValue);
	}
}

/*
 ==================
 CVar_FixCheatVariables
 ==================
*/
void CVar_FixCheatVariables (bool allowCheats){

	cvar_t	*cvar;

	if (cvar_allowCheats == allowCheats)
		return;
	cvar_allowCheats = allowCheats;

	if (cvar_allowCheats)
		return;

	for (cvar = cvar_variables; cvar; cvar = cvar->next){
		if (!(cvar->flags & CVAR_CHEAT))
			continue;

		if (!Str_ICompare(cvar->value, cvar->resetValue))
			continue;

		CVar_Set(cvar, cvar->resetValue, true);
	}
}

/*
 ==================
 CVar_AllowCheats
 ==================
*/
bool CVar_AllowCheats (void){

	return cvar_allowCheats;
}

/*
 ==================
 CVar_GetModifiedFlags
 ==================
*/
int CVar_GetModifiedFlags (void){

	return cvar_modifiedFlags;
}

/*
 ==================
 CVar_SetModifiedFlags
 ==================
*/
void CVar_SetModifiedFlags (int flags){

	cvar_modifiedFlags |= flags;
}

/*
 ==================
 CVar_ClearModifiedFlags
 ==================
*/
void CVar_ClearModifiedFlags (int flags){

	cvar_modifiedFlags &= ~flags;
}

/*
 ==================
 CVar_Command
 ==================
*/
bool CVar_Command (void){

	cvar_t *cvar;

	// Check variables
	cvar = CVar_FindVariable(Cmd_Argv(0));
	if (!cvar)
		return false;
		
	// Perform a variable print or set
	if (Cmd_Argc() == 1){
		Com_Printf("\"%s\" is: \"%s" S_COLOR_DEFAULT "\" default: \"%s" S_COLOR_DEFAULT "\"", cvar->name, cvar->value, cvar->resetValue);
		if (cvar->latchedValue)
			Com_Printf(" latched: \"%s" S_COLOR_DEFAULT "\"\n", cvar->latchedValue);
		else
			Com_Printf("\n");

		if (cvar->description)
			Com_Printf("%s\n", cvar->description);

		return true;
	}

	CVar_Set(cvar, Cmd_Argv(1), false);

	return true;
}

/*
 ==================
 CVar_InfoString
 ==================
*/
static const char *CVar_InfoString (int bit){

	static char	infoString[MAX_STRING_LENGTH];
	int			infoLength;
	char		pairString[MAX_STRING_LENGTH];
	int			pairLength;
	cvar_t		*cvar;

	infoLength = 0;

	for (cvar = cvar_variables; cvar; cvar = cvar->next){
		if (!(cvar->flags & bit))
			continue;

		// Add a new, \ terminated, name/value pair
		pairLength = Str_SPrintf(pairString, sizeof(pairString), "%s %s\\", cvar->name, cvar->value);

		if (pairLength < 0 || pairLength + infoLength >= sizeof(infoString)){
			Com_Printf(S_COLOR_YELLOW "CVar_InfoString: overflow\n");
			break;
		}

		memcpy(infoString + infoLength, pairString, pairLength);
		infoLength += pairLength;
	}

	infoString[infoLength] = 0;

	return infoString;
}

/*
 ==================
 CVar_UserInfoString
 ==================
*/
const char *CVar_UserInfoString (void){

	return CVar_InfoString(CVAR_USERINFO);
}

/*
 ==================
 CVar_ServerInfoString
 ==================
*/
const char *CVar_ServerInfoString (void){

	return CVar_InfoString(CVAR_SERVERINFO);
}


// ============================================================================


/*
 ==================
 CVar_SortCVarList
 ==================
*/
static int CVar_SortCVarList (const void *elem1, const void *elem2){

	const cvar_t	*cvar1 = *(const cvar_t **)elem1;
	const cvar_t	*cvar2 = *(const cvar_t **)elem2;

	return Str_Compare(cvar1->name, cvar2->name);
}

/*
 ==================
 CVar_Set_f
 ==================
*/
static void CVar_Set_f (void){

	cvar_t	*cvar;

	if (Cmd_Argc() != 3){
		Com_Printf("Usage: set <variable> <value>\n");
		return;
	}

	cvar = CVar_FindVariable(Cmd_Argv(1));
	if (!cvar){
		CVar_Get(Cmd_Argv(1), Cmd_Argv(2), 0, NULL);
		return;
	}

	CVar_Set(cvar, Cmd_Argv(2), false);
}

/*
 ==================
 CVar_Seta_f
 ==================
*/
static void CVar_Seta_f (void){

	cvar_t	*cvar;

	if (Cmd_Argc() != 3){
		Com_Printf("Usage: seta <variable> <value>\n");
		return;
	}

	cvar = CVar_FindVariable(Cmd_Argv(1));
	if (!cvar){
		CVar_Get(Cmd_Argv(1), Cmd_Argv(2), CVAR_ARCHIVE, NULL);
		return;
	}

	// Make it CVAR_ARCHIVE
	cvar->flags |= CVAR_ARCHIVE;

	CVar_Set(cvar, Cmd_Argv(2), false);
}

/*
 ==================
 CVar_Setu_f
 ==================
*/
static void CVar_Setu_f (void){

	cvar_t	*cvar;

	if (Cmd_Argc() != 3){
		Com_Printf("Usage: setu <variable> <value>\n");
		return;
	}

	cvar = CVar_FindVariable(Cmd_Argv(1));
	if (!cvar){
		CVar_Get(Cmd_Argv(1), Cmd_Argv(2), CVAR_USERINFO, NULL);
		return;
	}

	// Make it CVAR_USERINFO
	cvar->flags |= CVAR_USERINFO;

	CVar_Set(cvar, Cmd_Argv(2), false);
}

/*
 ==================
 CVar_Sets_f
 ==================
*/
static void CVar_Sets_f (void){

	cvar_t	*cvar;

	if (Cmd_Argc() != 3){
		Com_Printf("Usage: sets <variable> <value>\n");
		return;
	}

	cvar = CVar_FindVariable(Cmd_Argv(1));
	if (!cvar){
		CVar_Get(Cmd_Argv(1), Cmd_Argv(2), CVAR_SERVERINFO, NULL);
		return;
	}

	// Make it CVAR_SERVERINFO
	cvar->flags |= CVAR_SERVERINFO;

	CVar_Set(cvar, Cmd_Argv(2), false);
}

/*
 ==================
 CVar_Add_f
 ==================
*/
static void CVar_Add_f (void){

	cvar_t	*cvar;

	if (Cmd_Argc() != 3){
		Com_Printf("Usage: add <variable> <value>\n");
		return;
	}

	cvar = CVar_FindVariable(Cmd_Argv(1));
	if (!cvar){
		Com_Printf("'%s' is not a variable\n", Cmd_Argv(1));
		return;
	}

	CVar_Set(cvar, Str_FromFloat(cvar->floatValue + Str_ToFloat(Cmd_Argv(2))), false);
}

/*
 ==================
 CVar_Sub_f
 ==================
*/
static void CVar_Sub_f (void){

	cvar_t	*cvar;

	if (Cmd_Argc() != 3){
		Com_Printf("Usage: sub <variable> <value>\n");
		return;
	}

	cvar = CVar_FindVariable(Cmd_Argv(1));
	if (!cvar){
		Com_Printf("'%s' is not a variable\n", Cmd_Argv(1));
		return;
	}

	CVar_Set(cvar, Str_FromFloat(cvar->floatValue - Str_ToFloat(Cmd_Argv(2))), false);
}

/*
 ==================
 CVar_Toggle_f
 ==================
*/	
static void CVar_Toggle_f (void){

	cvar_t	*cvar;

	if (Cmd_Argc() != 2){
		Com_Printf("Usage: toggle <variable>\n");
		return;
	}

	cvar = CVar_FindVariable(Cmd_Argv(1));
	if (!cvar){
		Com_Printf("'%s' is not a variable\n", Cmd_Argv(1));
		return;
	}

	CVar_Set(cvar, Str_FromInteger(!cvar->integerValue), false);
}

/*
 ==================
 CVar_Reset_f
 ==================
*/
static void CVar_Reset_f (void){

	cvar_t	*cvar;

	if (Cmd_Argc() != 2){
		Com_Printf("Usage: reset <variable>\n");
		return;
	}

	cvar = CVar_FindVariable(Cmd_Argv(1));
	if (!cvar){
		Com_Printf("'%s' is not a variable\n", Cmd_Argv(1));
		return;
	}

	CVar_Set(cvar, cvar->resetValue, false);
}

/*
 ==================
 CVar_ListCVars_f
 ==================
*/
static void CVar_ListCVars_f (void){

	cvar_t		*cvar, *list[MAX_LIST_CVARS];
	bool		help = false, flags = false;
	const char	*filter = NULL;
	int			i, found = 0, total = 0;

	if (Cmd_Argc() > 3){
		Com_Printf("Usage: listCVars [\"-help\" | \"-flags\"] [filter]\n");
		return;
	}

	if (Cmd_Argc() == 2){
		if (!Str_ICompare("-help", Cmd_Argv(1)))
			help = true;
		else if (!Str_ICompare("-flags", Cmd_Argv(1)))
			flags = true;
		else
			filter = Cmd_Argv(1);
	}
	else if (Cmd_Argc() == 3){
		if (!Str_ICompare("-help", Cmd_Argv(1)))
			help = true;
		else if (!Str_ICompare("-flags", Cmd_Argv(1)))
			flags = true;
		else {
			Com_Printf("Usage: listCVars [\"-help\" | \"-flags\"] [filter]\n");
			return;
		}

		filter = Cmd_Argv(2);
	}

	// Find matching variables
	for (cvar = cvar_variables; cvar; cvar = cvar->next){
		total++;

		if (filter){
			if (!Str_MatchFilter(cvar->name, filter, false))
				continue;
		}

		list[found++] = cvar;
	}

	// Sort the list
	qsort(list, found, sizeof(cvar_t *), CVar_SortCVarList);

	// Print the list
	for (i = 0; i < found; i++){
		cvar = list[i];

		Com_Printf("    %-32s", cvar->name);

		if (help){
			if (cvar->description)
				Com_Printf(" %s\n", cvar->description);
			else
				Com_Printf("\n");

			continue;
		}

		if (flags){
			if (cvar->flags & CVAR_ARCHIVE)
				Com_Printf(" AR");
			else
				Com_Printf("   ");

			if (cvar->flags & CVAR_USERINFO)
				Com_Printf(" UI");
			else
				Com_Printf("   ");

			if (cvar->flags & CVAR_SERVERINFO)
				Com_Printf(" SI");
			else
				Com_Printf("   ");

			if (cvar->flags & CVAR_INIT)
				Com_Printf(" IN");
			else
				Com_Printf("   ");

			if (cvar->flags & CVAR_ROM)
				Com_Printf(" RO");
			else
				Com_Printf("   ");

			if (cvar->flags & CVAR_CHEAT)
				Com_Printf(" CH");
			else
				Com_Printf("   ");

			if (cvar->flags & CVAR_LATCH)
				Com_Printf(" LA");
			else
				Com_Printf("   ");

			Com_Printf("\n");

			continue;
		}

		Com_Printf(" \"%s\"\n", cvar->value);
	}

	Com_Printf("--------------------\n");
	Com_Printf("%i cvars listed (%i total cvars)\n", found, total);
}

/*
 ==================
 CVar_Restart_f
 ==================
*/
static void CVar_Restart_f (void){

	cvar_t	*cvar;

	for (cvar = cvar_variables; cvar; cvar = cvar->next){
		if (cvar->flags & (CVAR_INIT | CVAR_ROM))
			continue;

		if (cvar->flags & CVAR_LATCH)
			CVar_Set(cvar, cvar->resetValue, false);
		else
			CVar_Set(cvar, cvar->resetValue, true);
	}
}

/*
 ==================
 CVar_Init
 ==================
*/
void CVar_Init (void){

	Cmd_AddCommand("set", CVar_Set_f, "Sets a variable");
	Cmd_AddCommand("seta", CVar_Seta_f, "Sets a variable flagged as archive");
	Cmd_AddCommand("setu", CVar_Setu_f, "Sets a variable flagged as user info");
	Cmd_AddCommand("sets", CVar_Sets_f, "Sets a variable flagged as server info");
	Cmd_AddCommand("add", CVar_Add_f, "Adds a value to a variable");
	Cmd_AddCommand("sub", CVar_Sub_f, "Subtracts a value from a variable");
	Cmd_AddCommand("toggle", CVar_Toggle_f, "Toggles a variable");
	Cmd_AddCommand("reset", CVar_Reset_f, "Resets a variable");
	Cmd_AddCommand("listCVars", CVar_ListCVars_f, "Lists variables");
	Cmd_AddCommand("restartCVars", CVar_Restart_f, "Restarts the cvar system");
}

/*
 ==================
 CVar_Shutdown
 ==================
*/
void CVar_Shutdown (void){

	Cmd_RemoveCommand("set");
	Cmd_RemoveCommand("seta");
	Cmd_RemoveCommand("setu");
	Cmd_RemoveCommand("sets");
	Cmd_RemoveCommand("add");
	Cmd_RemoveCommand("sub");
	Cmd_RemoveCommand("toggle");
	Cmd_RemoveCommand("reset");
	Cmd_RemoveCommand("listCVars");
	Cmd_RemoveCommand("restartCVars");
}
